#include <glib.h>
#include <gio/gio.h>
#include <gio/gnetworking.h>
#include "sql.h"
#include "tsres.h"
#include "sqlite3.h"
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#include <libsoup/soup.h>
#define DB_NAME "account.db"
#define RESOURCE_DB_NAME "resource.db"
#define SERVER_PORT 12222
gpointer account_db = NULL, resource_db = NULL;
GList *clients = NULL, *resource = NULL;
int tmp_sql_callback(void *ptr, int n_column,
    char **column_value, char **column_name)
{
  int rc = 0;//, i;

  if (n_column == 0) {
    printf("sql: column is 0\n");
    rc = 1;
    goto err;
  }
  if (ptr) {
    GList *l = NULL, **lp = NULL;
    gchar *utf8 = NULL;
    gunichar *ucs4 = NULL;
    TsRes *res = NULL;
    GError *error = NULL;
    lp = ptr;
    l = *lp;
 //   for (i = 0; i < n_column; i++) {
      utf8 = column_value[1];
      if (utf8) {
        if (strlen (utf8) > 0) {
          ucs4 = g_utf8_to_ucs4 (utf8, -1, NULL, NULL, &error);
          if (error) {
            g_warning ("L%d f-%s %s", __LINE__, __func__, error->message);
            g_clear_error (&error);
          } else {
            res = ts_res_new (0, 0, utf8, ucs4, NULL);
            l = g_list_append (l, res);
          }
        }
      }
    //}
    *lp = l;
  }
err:
  return rc;
}
GList *res_match (GList *re, gunichar *ucs4)
{
  gint match = 0, i, j, now_match, next_match;
  GList *start = NULL, *now = NULL;
  gunichar *u = NULL;
  TsRes *res = NULL, *new = NULL;

  while (re) {
    match = 0;
    res = re->data;
    g_object_get (res, "ucs4", &u, NULL);
    for (i = 0; ucs4[i] != 0; i++) {
      for (j = 0; u[j] != 0; j++) {
        if (ucs4[i] == u[j]) {
          match++;
          break;
        }
      }
    }
    if (match) {
      new = ts_res_new (0, match, NULL, NULL, res);
      g_debug ("res:%p match %d", res, match);
      if (start) {
        now = start;
        while (now) {
          g_object_get (now->data, "match", &now_match, NULL);
          if (match > now_match) {
            start = g_list_insert_before (start, now, new);
            break;
          }
          if (now->next) {
            g_object_get (now->next->data, "match", &next_match, NULL);
            if ((match < now_match) && (match >= next_match)) {
              start = g_list_insert_before (start, now->next, new);
              break;
            }
          } else {
            start = g_list_append (start, new);
            break;
          }
          now = now->next;
        }
      } else {
        start = g_list_append (NULL, new);
      }
    }
    re = re->next;
  }
  return start;
}
static char *
post_account_sign_in (const gchar *msg, gsize size, void *sql)
{
  xmlDocPtr doc = NULL;
  xmlNodePtr root = NULL, child = NULL;
  xmlBufferPtr xmlbuf = NULL;
  xmlChar *id = NULL, *pwd = NULL;
  gchar buf[1024] = {0}, verification[32] = {0};
  gpointer res = NULL;
  gint rc = 0;

  //g_debug ("sql:%p", sql);
  doc = xmlParseDoc (BAD_CAST msg);
  root = xmlDocGetRootElement (doc);
  child = xmlFirstElementChild (root);
  id = xmlGetProp (child, BAD_CAST "id");
  pwd = xmlGetProp (child, BAD_CAST "pwd");
  root = NULL;
  g_snprintf(buf, sizeof(buf),
      "select 1 from %s where id='%s' and pwd='%s';",
      "account", (char *)id, (char *)pwd);
  rc = sql_exec_count(sql, buf);
  root = xmlNewNode (NULL, BAD_CAST "root");
  child = xmlNewChild (root, NULL, BAD_CAST "child", NULL);
  xmlbuf = xmlBufferCreate ();
  if (rc) {
    rc = g_random_int_range(0, 0xffff);
    g_snprintf(verification, sizeof verification, "%d", rc);
    g_snprintf(buf, sizeof(buf),
        "update %s set verification='%s' where id='%s' and pwd='%s';",
        "account", verification, (char *)id, (char *)pwd);
    res = sql_exec(sql, buf);
    g_free(res);
    xmlNewProp (child, BAD_CAST "token", BAD_CAST verification);
  } else {
    xmlNewProp (child, BAD_CAST "error", BAD_CAST "id or pwd error");
  }
  rc = xmlNodeDump (xmlbuf, NULL, root, 0, 0);
  if (rc == -1) {
    g_warning ("xmlnodedump error\n");
    goto out;
  }
  if (xmlbuf)
    res = g_strdup ((char *)xmlbuf->content);
  goto out;
out:
  if (xmlbuf)
    xmlBufferFree (xmlbuf);
  if (root)
    xmlFreeNode (root);
  if (doc)
    xmlFreeDoc(doc);
  if (id)
    xmlFree (id);
  if (pwd)
    xmlFree (pwd);
  return res;
}
gboolean o_sign (void *o, gint id, const gchar *token, const gchar *name)
{
  gboolean rc = FALSE;
  xmlNodePtr root = NULL, child = NULL;
  xmlBufferPtr xmlbuf = NULL;
  gchar buf[1024] = {0};

  g_snprintf(buf, sizeof(buf),
      "select 1 from account where id =%"G_GINT32_FORMAT" and verification='%s';",
      id, token);
  rc = sql_exec_count(account_db, buf);
  root = xmlNewNode (NULL, BAD_CAST name);
  child = xmlNewChild (root, NULL, BAD_CAST "child", NULL);
  if (!rc) {
    xmlNewProp (child, BAD_CAST "error", BAD_CAST "no sign");
  }
  xmlbuf = xmlBufferCreate ();
  rc = xmlNodeDump (xmlbuf, NULL, root, 0, 0);
  if (rc == -1) {
    g_warning ("xmlnodedump error\n");
    goto out;
  }
  soup_websocket_connection_send_text (o, (char *)xmlbuf->content);
  g_print ("----[websocket]:%s\n", (char *)xmlbuf->content);
out:
  if (xmlbuf)
    xmlBufferFree (xmlbuf);
  if (root)
    xmlFreeNode (root);
  return rc;
}
void o_no_sign (void *o, const gchar *name)
{
  xmlNodePtr root = NULL, child = NULL;
  xmlBufferPtr xmlbuf = NULL;
  gint rc;

  root = xmlNewNode (NULL, BAD_CAST name);
  child = xmlNewChild (root, NULL, BAD_CAST "child", NULL);
  xmlNewProp (child, BAD_CAST "error", BAD_CAST "no sign");
  xmlbuf = xmlBufferCreate ();
  rc = xmlNodeDump (xmlbuf, NULL, root, 0, 0);
  if (rc == -1) {
    g_warning ("xmlnodedump error");
    goto out;
  }
  soup_websocket_connection_send_text (o, (char *)xmlbuf->content);
  g_print ("----[websocket]:%s\n", (char *)xmlbuf->content);
out:
  if (xmlbuf)
    xmlBufferFree (xmlbuf);
  if (root)
    xmlFreeNode (root);
  return;
}
gboolean o_chat (void *o, gint id, const gchar *name, const gchar *msg)
{
  gint tmp_match;
  gboolean rc = FALSE;
  gchar *utf8 = NULL;
  gunichar *ucs4 = NULL;
  TsRes *res = NULL, *new = NULL;
  GList *l = NULL;
  GError *error = NULL;
  xmlNodePtr root = NULL, child = NULL;
  xmlBufferPtr xmlbuf = NULL;
  //gchar buf[1024] = {0};

  ucs4 = g_utf8_to_ucs4 (msg, -1, NULL, NULL, &error);
  if (error) {
    g_warning ("L%d f-%s %s", __LINE__, __func__, error->message);
    g_clear_error (&error);
  } else {
    l = res_match (resource, ucs4);
  }
  root = xmlNewNode (NULL, BAD_CAST name);
  while (l) {
    new = l->data;
    g_object_get (new, "symlink", &res, "match", &tmp_match, NULL);
    g_debug ("match:%d", tmp_match);
    if (res) {
      g_debug ("L%d f-%s res:%p", __LINE__, __func__, res);
      g_object_get (res, "utf8", &utf8, NULL);
      child = xmlNewChild (root, NULL, BAD_CAST "child", NULL);
      xmlNewProp (child, BAD_CAST "msg", BAD_CAST utf8);
      if (utf8)
        g_free (utf8);
    }
    l = l->next;
  }
  xmlbuf = xmlBufferCreate ();
  rc = xmlNodeDump (xmlbuf, NULL, root, 0, 0);
  if (rc == -1) {
    g_warning ("xmlnodedump error\n");
    goto out;
  }
  soup_websocket_connection_send_text (o, (char *)xmlbuf->content);
  g_print ("----[websocket]:%s\n", (char *)xmlbuf->content);
out:
  if (xmlbuf)
    xmlBufferFree (xmlbuf);
  if (root)
    xmlFreeNode (root);
  return rc;
}
void
handle_text (SoupWebsocketConnection *connection, const gchar *data)
{
  xmlDocPtr doc = NULL;
  xmlNodePtr root = NULL, child = NULL;
  xmlChar *tmp_str = NULL;
  gint rc = 0, status = 0, id = 0, read_id = 0;//
  const gchar *name = NULL;
  gpointer status_p, id_p;

  status_p = g_object_get_data (G_OBJECT (connection), "status");
  if (status_p)
    status = GPOINTER_TO_INT (status_p);
  id_p = g_object_get_data (G_OBJECT (connection), "id");
  if (id_p)
    id = GPOINTER_TO_SIZE (id_p);
  doc = xmlParseDoc (BAD_CAST data);
  root = xmlDocGetRootElement (doc);
  child = xmlFirstElementChild (root);

  name = (char *)root->name;
  tmp_str = xmlGetProp (child, BAD_CAST "id");
  if (tmp_str) {
    read_id = atoi ((char *)tmp_str);
    xmlFree (tmp_str);
  }
  if (!g_strcmp0 (name, "sign")) {
    tmp_str = xmlGetProp (child, BAD_CAST "token");
    rc = o_sign (connection, read_id, (char *)tmp_str, name);
    if (tmp_str)
      xmlFree (tmp_str);
    if (rc) {
      g_object_set_data (G_OBJECT (connection), "status", GINT_TO_POINTER(1));
      g_object_set_data (G_OBJECT (connection), "id", GINT_TO_POINTER(read_id));
    }
    goto out;
  }
  if (status < 1) {
    o_no_sign(connection, name);
    //goto out;
  }
  if (!g_strcmp0 (name, "test")) {
    g_warning ("test name:%s", (char *)root->name);
  } else if (!g_strcmp0 (name, "send")) {
    tmp_str = xmlGetProp (child, BAD_CAST "msg");
    o_chat (connection, id, name, (char *)tmp_str);
    if (tmp_str) {
      xmlFree (tmp_str);
    }
  } else {
    g_warning ("invalid name:%s", (char *)root->name);
  }
out:
  if (doc)
    xmlFreeDoc(doc);
}
static void
on_text_message (SoupWebsocketConnection *connection,
    SoupWebsocketDataType type,
    GBytes *message,
    gpointer user_data)
{
  gsize len;
  const gchar *data = g_bytes_get_data(message, &len);

  if (type == SOUP_WEBSOCKET_DATA_TEXT) {
    g_print("++++[websocket]: %.*s\n", (int)len, data);
//    soup_websocket_connection_send_text (connection, data);
    handle_text (connection, data);
  }
}
static void
on_close_unref_connection (SoupWebsocketConnection *connection,
    gpointer user_data)
{
  clients = g_list_remove (clients, connection);
  g_print("WebSocket connection closed\n");
  g_clear_object (&connection);
}
static void
got_server_connection (SoupServer *server,
    SoupWebsocketConnection *connection,
    const char *path,
    SoupClientContext *context,
    gpointer user_data)
{
  g_print("WebSocket connected\n");

  clients = g_list_append (clients, connection);
  g_object_ref (connection);
  g_signal_connect(connection, "message", G_CALLBACK(on_text_message), NULL);
  g_signal_connect(connection, "closed", G_CALLBACK(on_close_unref_connection),
      NULL);
}
static void
forget_close (SoupMessage *msg, gpointer user_data)
{
  soup_message_headers_remove (msg->response_headers, "Connection");
}

static void
close_socket (SoupMessage *msg, gpointer user_data)
{
  SoupSocket *sock = user_data;
  int sockfd;
  sockfd = soup_socket_get_fd (sock);
#ifdef G_OS_WIN32
  shutdown (sockfd, SD_SEND);
#else
  shutdown (sockfd, SHUT_WR);
#endif
  soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC,
                            "foo", 3);
}

static void
timeout_socket (SoupSocket *sock, gpointer user_data)
{
  soup_socket_disconnect (sock);
}
static void
timeout_request_started (SoupServer *server, SoupMessage *msg,
                         SoupClientContext *client, gpointer user_data)
{
  SoupSocket *sock;
  GMainContext *context = g_main_context_get_thread_default ();
  guint readable;

  g_signal_handlers_disconnect_by_func (server, timeout_request_started, NULL);

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
  sock = soup_client_context_get_socket (client);
  G_GNUC_END_IGNORE_DEPRECATIONS;
  readable = g_signal_connect (sock, "readable",
                              G_CALLBACK (timeout_socket), NULL);

  while (soup_socket_is_connected (sock))
          g_main_context_iteration (context, TRUE);
  g_signal_handler_disconnect (sock, readable);
}

static void
setup_timeout_persistent (SoupServer *server, SoupSocket *sock)
{
  char buf[1];
  gsize nread;

  soup_socket_read (sock, buf, 1, &nread, NULL, NULL);
  g_signal_connect (server, "request-started",
                    G_CALLBACK (timeout_request_started), NULL);
}


static void
server_callback (SoupServer *server, SoupMessage *msg,
    const char *path, GHashTable *query,
    SoupClientContext *context, gpointer user_data)
{
  gchar *file_content = NULL;
  gsize file_length = 0;
  GError *error = NULL;

  if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) {
    soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
    return;
  }

  if (g_str_has_prefix (path, "/content-length/")) {
    gboolean too_long = strcmp (path, "/content-length/long") == 0;
    gboolean no_close = strcmp (path, "/content-length/noclose") == 0;

    soup_message_set_status (msg, SOUP_STATUS_OK);
    soup_message_set_response (msg, "text/plain",
                               SOUP_MEMORY_STATIC, "foobar", 6);
    if (too_long)
      soup_message_headers_set_content_length (msg->response_headers, 9);
    soup_message_headers_append (msg->response_headers,
                                 "Connection", "close");

    if (too_long) {
      SoupSocket *sock;
      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
      sock = soup_client_context_get_socket (context);
      G_GNUC_END_IGNORE_DEPRECATIONS;
      g_signal_connect (msg, "wrote-chunk",
                        G_CALLBACK (close_socket), sock);
    } else if (no_close) {
      g_signal_connect (msg, "wrote-headers",
                    G_CALLBACK (forget_close), NULL);
    }
      return;
  }

  if (!strcmp (path, "/timeout-persistent")) {
    SoupSocket *sock;

    G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
    sock = soup_client_context_get_socket (context);
    G_GNUC_END_IGNORE_DEPRECATIONS;
    setup_timeout_persistent (server, sock);
  }

  soup_message_headers_append(msg->response_headers,
      "Access-Control-Allow-Origin", "*");
  gchar *str = NULL;
  if (msg->method == SOUP_METHOD_POST) {
    soup_message_set_status (msg, SOUP_STATUS_OK);
    g_debug ("post path:%s", path);
    g_debug ("post data:%s", msg->request_body->data);
    if (!g_strcmp0(path, "/test")) {
      g_warning ("test:%s", path);
    } else if (!g_strcmp0(path, "/ts/account/sign_in")) {
      str = post_account_sign_in (msg->request_body->data, 0, user_data);
    } else {
      g_warning ("invalid path:%s", path);
    }
  } else if (msg->method == SOUP_METHOD_GET) {
    g_file_get_contents (path + 1, &file_content, &file_length, &error);
    g_debug ("path:%s file_length :%"G_GSIZE_FORMAT, path + 1, file_length);
    soup_message_body_append(msg->response_body, SOUP_MEMORY_TAKE,
                                     file_content, file_length);
    soup_message_set_status (msg, SOUP_STATUS_OK);
  } else {
    g_debug ("path :%s", path);
    str = g_strdup ("waitting to support.");
  }
  if (str) {
    soup_message_set_response (msg, "text/plain",
                               SOUP_MEMORY_STATIC, str, strlen (str));
    g_debug ("L%d f-%s response:%s", __LINE__, __func__, str);
  }
  goto out;
out:
  return;
}
int
main (int argc, char *argv[])
{
	g_setenv ("GSETTINGS_SCHEMA_DIR", ".", FALSE);
	g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);

  int rc = 0;
	sqlite3 *sql = NULL;
  gchar *errmsg = NULL, buf[1024] = {0};
//sql
  rc = sqlite3_open(DB_NAME, &sql);
  if (rc != SQLITE_OK) {
    perror(sqlite3_errmsg(sql));
    goto out;
  }
	g_print("sqlname:%s %p\n", DB_NAME, sql);
  account_db = sql;
//sources sql
  rc = sqlite3_open(RESOURCE_DB_NAME, (sqlite3 **)&resource_db);
  if (rc != SQLITE_OK) {
    perror(sqlite3_errmsg(resource_db));
    goto out;
  }
	g_print("sqlname:%s %p\n", RESOURCE_DB_NAME, resource_db);
  g_snprintf (buf, sizeof (buf), "select * from res;");
  rc = sqlite3_exec(resource_db, buf, tmp_sql_callback, &resource, &errmsg);
  if (rc != SQLITE_OK) {
    errno = rc;
    g_warning("SELECT:%s:%s:%d\n", buf, errmsg, rc);
    goto out;
  }
//server
  SoupServer *server = soup_server_new(SOUP_SERVER_SERVER_HEADER,
      "MyWebSocketServer", NULL);
  soup_server_add_handler(server, NULL, server_callback, (void *)sql, NULL);
  soup_server_add_websocket_handler(server, "/websocket", NULL,
      NULL, got_server_connection, NULL, NULL);
  soup_server_listen_all(server, SERVER_PORT, 0, NULL);
  g_debug ("server port:%d", SERVER_PORT);

  GMainLoop *loop = g_main_loop_new(NULL, FALSE);

  g_main_loop_run(loop);
  g_main_loop_unref(loop);
  g_object_unref(server);
out:
  return rc;
}
